/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2006 Ola Bini <Ola.Bini@ki.se>
 * Copyright (C) 2006 Tim Azzopardi <tim@tigerfive.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2007 MenTaLguY <mental@rydia.net>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package asruby.lang {

import asruby.Ruby;
import asruby.runtime.Arity;
import asruby.runtime.Block;
import asruby.runtime.CallbackFactory;
import asruby.runtime.ClassIndex;
import asruby.runtime.IRubyObject;
import asruby.runtime.ThreadContext;

import flash.utils.Dictionary;

// Design overview:
//
// RubyHash is implemented as hash table with a singly-linked list of
// RubyHash.RubyHashEntry objects for each bucket.  RubyHashEntry objects
// are also kept in a doubly-linked list which reflects their insertion
// order and is used for iteration.  For simplicity, this latter list is
// circular; a dummy RubyHashEntry, RubyHash.head, is used to mark the
// ends of the list.
//
// When an entry is removed from the table, it is also removed from the
// doubly-linked list.  However, while the reference to the previous
// RubyHashEntry is cleared (to mark the entry as dead), the reference
// to the next RubyHashEntry is preserved so that iterators are not
// invalidated: any iterator with a reference to a dead entry can climb
// back up into the list of live entries by chasing next references until
// it finds a live entry (or head).
//
// Ordinarily, this scheme would require O(N) time to clear a hash (since
// each RubyHashEntry would need to be visited and unlinked from the
// iteration list), but RubyHash also maintains a generation count.  Every
// time the hash is cleared, the doubly-linked list is simply discarded and
// the generation count incremented.  Iterators check to see whether the
// generation count has changed; if it has, they reset themselves back to
// the new start of the list.
//
// This design means that iterators are never invalidated by changes to the
// hashtable, and they do not need to modify the structure during their
// lifecycle.
//

/** Implementation of the Hash class.
 *
 *  Concurrency: no synchronization is required among readers, but
 *  all users must synchronize externally with writers.
 *
 *
      "A Hash</code> is a collection of key-value pairs. It is\n"+
      "similar to an Array</code>, except that indexing is done via\n"+
      "arbitrary keys of any object type, not an integer index. The order\n"+
      "in which you traverse a hash by either key or value may seem\n"+
      "arbitrary, and will generally not be in the insertion order.\n"+
      "   \n"+
      "Hashes have a default value</em> that is returned when accessing\n"+
      "keys that do not exist in the hash. By default, that value is\n"+
      "nil</code>.\n"+
      "\n"+
      "Hash</code> uses key.eql?</code> to test keys for equality.\n"+
      "If you need to use instances of your own classes as keys in a Hash</code>,\n"+
      "it is recommended that you define both the eql?</code> and hash</code>\n"+
      "methods. The hash</code> method must have the property that \n"+
      "a.eql?(b)</code> implies a.hash == b.hash</code>.\n"+
      "\n"+
      "  class MyClass\n"+
      "    attr_reader :str\n"+
      "    def initialize(str)\n"+
      "      @str = str\n"+
      "    end\n"+
      "    def eql?(o)\n"+
      "      o.is_a?(MyClass) && str == o.str\n"+
      "    end\n"+
      "    def hash\n"+
      "      @str.hash\n"+
      "    end\n"+
      "  end\n"+
      "  \n"+
      "  a = MyClass.new(\"some string\")\n"+
      "  b = MyClass.new(\"some string\")\n"+
      "  a.eql? b  #=> true\n"+
      "  \n"+
      "  h = {}\n"+
      "  \n"+
      "  h[a] = 1\n"+
      "  h[a]      #=> 1\n"+
      "  h[b]      #=> 1\n"+
      "  \n"+
      "  h[b] = 2\n"+
      "  h[a]      #=> 2\n"+
      "  h[b]      #=> \n2",
      name = "Hash"
*/

public class RubyHash extends RubyObject {

    public static function createHashClass(runtime:Ruby):RubyClass{
        var hashc: RubyClass =  runtime.defineClass("Hash", runtime.getObject(), HASH_ALLOCATOR);
        runtime.setHash(hashc);
        hashc.index = ClassIndex.HASH;
        hashc.kindOf = 
        	function isKindOf(obj:IRubyObject, type:RubyModule):Boolean{
                return obj instanceof RubyHash;
            };
       
        var callbackFactory: CallbackFactory =  runtime.callbackFactory(RubyHash);

        hashc.includeModule(runtime.getEnumerable());

        hashc.defineAnnotatedMethods(RubyHash);
        hashc.dispatcher = callbackFactory.createDispatcher(hashc);

        return hashc;
    }

    private static var HASH_ALLOCATOR: Function = 
        function allocate(runtime:Ruby, klass:RubyClass):IRubyObject{
            return new RubyHash(runtime, klass);
        };
    
    public override function getNativeTypeIndex():int{
        return ClassIndex.HASH;
    }

    /** rb_hash_s_create
     *
     */
    public static function create(context:ThreadContext, recv:IRubyObject, args:Array, block:Block):IRubyObject{
        var klass: RubyClass =   recv;
        var hash:RubyHash;

        if (args.length == 1 && args[0] instanceof RubyHash) {
            var otherHash: RubyHash =  args[0];
            return new RubyHash(recv.getRuntime(), klass, otherHash);
        }

        if ((args.length & 1) != 0) throw recv.getRuntime().newArgumentError("odd number of args for Hash");

        hash = klass.allocate();
        for ( var int:i =0; i < args.length; i+=2) hash.op_aset(context, args[i], args[i+1]);

        return hash;
    }

    /** rb_hash_new
     *
     */
    public static function newHash(runtime:Ruby):RubyHash{
        return new RubyHash(runtime);
    }

    /** rb_hash_new
     *
     */
    public static function newHash2(runtime:Ruby, valueMap:Dictionary, defaultValue:IRubyObject):RubyHash{
        //assert defaultValue != null;

        return new RubyHash(runtime, valueMap, defaultValue);
    }

    private  var table:Array;
    //private  var size: int =  0;
    private  var threshold:int;

    private static var PROCDEFAULT_HASH_F: int =  1 << 10;

    private  var ifNone:IRubyObject;

 /*    private  function RubyHash0(runtime:Ruby, klass:RubyClass, other:RubyHash){
        super(runtime, klass);
        this.ifNone = runtime.getNil();
        threshold = INITIAL_THRESHOLD;
        table = other.internalCopyTable(head);
        size = other.size;
    }

    public  function RubyHash1(runtime:Ruby, klass:RubyClass){
        super(runtime, klass);
        this.ifNone = runtime.getNil();
        alloc();
    }

    public  function RubyHash2(runtime:Ruby){
        this(runtime, runtime.getNil());
    }

    public  function RubyHash3(runtime:Ruby, defaultValue:IRubyObject){
        super(runtime, runtime.getHash());
        this.ifNone = defaultValue;
        alloc();
    }

    function RubyHash4(runtime:Ruby, objectSpace:Boolean){
        super(runtime, runtime.getHash(), objectSpace);
        alloc();
    } */

    // TODO should this be deprecated ? (to be efficient, internals should deal with RubyHash directly)
    public  function RubyHash(runtime:Ruby, valueMap:Dictionary, defaultValue:IRubyObject){
        super(runtime, runtime.getHash());
        this.ifNone = defaultValue;
        alloc();

        for ( var key:Object in valueMap ) {           
            internalPut(key, valueMap[key]);
        }
    }

    private  function alloc():void{
        threshold = INITIAL_THRESHOLD;
        generation++;
        head.nextAdded = head.prevAdded = head;
        table = new RubyHashEntry[MRI_HASH_RESIZE ? MRI_INITIAL_CAPACITY : JAVASOFT_INITIAL_CAPACITY];
    }
    
    /*  ================
     *  Instance Methods
     *  ================
     */

    /** rb_hash_initialize
     *
     */
    public override function initialize(args:Array, block:Block):IRubyObject{
        modify();

        if (block.isGiven()) {
            if (args.length > 0) throw getRuntime().newArgumentError("wrong number of arguments");
            ifNone = getRuntime().newProc(Block.Type.PROC, block);
            flags |= PROCDEFAULT_HASH_F;
        } else {
            Arity.checkArgumentCount(getRuntime(), args, 0, 1);
            if (args.length == 1) ifNone = args[0];
        }
        return this;
    }

    /** rb_hash_default
     *
     */
    public  function default_value_get(context:ThreadContext, args:Array):IRubyObject{
        if ((flags & PROCDEFAULT_HASH_F) != 0) {
            if (args.length == 0) return getRuntime().getNil();
            return ifNone.callMethod(context, "call", [this, args[0]]);
        }
        return ifNone;
    }

    /** rb_hash_set_default
     *
     */
    public  function default_value_set(defaultValue:IRubyObject):IRubyObject{
        modify();

        ifNone = defaultValue;
        flags &= ~PROCDEFAULT_HASH_F;

        return ifNone;
    }

    /** rb_hash_default_proc
     *
     */
    public  function default_proc():IRubyObject{
        return (flags & PROCDEFAULT_HASH_F) != 0 ? ifNone : getRuntime().getNil();
    }

    /** rb_hash_modify
     *
     */
    public  function modify():void{
    	testFrozen("hash");
        if (isTaint() && getRuntime().getSafeLevel() >= 4) {
            throw getRuntime().newSecurityError("Insecure: can't modify hash");
        }
    }

    /** inspect_hash
     *
     */
    public  function inspectHash(context:ThreadContext):IRubyObject{
        var runtime: Ruby =  getRuntime();
        var sb: StringBuffer =  new StringBuffer("{");
        var firstEntry: Array =  new boolean[1];

        firstEntry[0] = true;
        visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                if (!firstEntry[0]) sb.append(", ");
                sb.append(key.callMethod(context, "inspect")).append("=>");
                sb.append(value.callMethod(context, "inspect"));
                firstEntry[0] = false;
            }
        );
        sb.append("}");
        return runtime.newString(sb.toString());
    }


/*     public override function inspect(context:ThreadContext):IRubyObject{
        if (size == 0) return getRuntime().newString("{}");
        if (getRuntime().isInspecting(this)) return getRuntime().newString("{...}");

        try {
            getRuntime().registerInspecting(this);
            return inspectHash(context);
        } finally { getRuntime().unregisterInspecting(this);
        }
    } */

    /** rb_hash_size
     *
     */
    public  function rb_size():RubyFixnum{
        return getRuntime().newFixnum(size);
    }

    /** rb_hash_empty_p
     *
     */
    public  function empty_p():RubyBoolean{
        return size == 0 ? getRuntime().getTrue() : getRuntime().getFalse();
    }

    /** rb_hash_to_a
     *
     */
    public override function to_a():RubyArray{
        var runtime: Ruby =  getRuntime();
        var result: RubyArray =  RubyArray.newArray(runtime, size);

        visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                result.append(RubyArray.newArray(runtime, key, value));
            }
        );

        result.setTaint(isTaint());
        return result;
    }

    /** rb_hash_to_s & to_s_hash
     *
     */
    public override function to_s():IRubyObject{
        if (getRuntime().isInspecting(this)) return getRuntime().newString("{...}");
        try {
            getRuntime().registerInspecting(this);
            return to_a().to_s();
        } finally {
            getRuntime().unregisterInspecting(this);
        }
    }

    /** rb_hash_rehash
     *
     */
    public  function rehash():RubyHash{
        modify();
        var oldTable: Array =  table;
        var newTable: Array =  new RubyHashEntry[oldTable.length];
        for ( var int:j = 0; j < oldTable.length; j++) {
            var entry: RubyHashEntry =  oldTable[j];
            oldTable[j] = null;
            while (entry != null) {
                var next: RubyHashEntry =  entry.next;
                entry.hash = entry.key.hashCode(); // update the hash value
                var i: int =  bucketIndex(entry.hash, newTable.length);
                entry.next = newTable[i];
                newTable[i] = entry;
                entry = next;
            }
        }
        table = newTable;
        return this;
    }

    /** rb_hash_to_hash
     *
     */
    public  function to_hash():RubyHash{
        return this;
    }

/*     public override function convertToHash():RubyHash{
        return this;
    } */

    public  function fastASet(key:IRubyObject, value:IRubyObject):void{
        internalPut(key, value);
    }

    public  function op_aset(key:IRubyObject, value:IRubyObject):IRubyObject{
        return op_aset(getRuntime().getCurrentContext(), key, value);
    }

    /** rb_hash_aset
     *
     */
    public function op_aset2(context:ThreadContext, key:IRubyObject, value:IRubyObject):IRubyObject{
        modify();

        if (!(key instanceof RubyString)) {
            internalPut(key, value);
        } else {
            var entry: RubyHashEntry =  internalGetEntry(key);
            if (entry != NO_ENTRY) {
                entry.value = value;
            } else {
                var realKey: RubyString =  key;
                
                if (!realKey.isFrozen()) {
                    realKey = realKey.strDup(realKey.getMetaClass().getRealClass());;
                    realKey.setFrozen(true);
                }
                
                internalPut(realKey, value, false);
            }
        }

        return value;
    }

    /**
     * Note: this is included as a compatibility measure for AR-JDBC
     * @deprecated use RubyHash.op_aset instead
     */
    public  function aset(key:IRubyObject, value:IRubyObject):IRubyObject{
        return op_aset(getRuntime().getCurrentContext(), key, value);
    }

    /**
     * Note: this is included as a compatibility measure for Mongrel+JRuby
     * @deprecated use RubyHash.op_aref instead
     */
    public  function aref(key:IRubyObject):IRubyObject{
        return op_aref(getRuntime().getCurrentContext(), key);
    }

    public function fastARef( key:IRubyObject):IRubyObject { // retuns null when not found to avoid unnecessary getRuntime().getNil() call
        return internalGet(key);
    }

    /** rb_hash_aref
     *
     */
    public  function op_aref(context:ThreadContext, key:IRubyObject):IRubyObject{
        var value:IRubyObject;
        return ((value = internalGet(key)) == null) ? callMethod(context, MethodIndex.DEFAULT, "default", key) : value;
    }

    /** rb_hash_fetch
     *
     */
    public  function fetch(context:ThreadContext, args:Array, block:Block):IRubyObject{
        if (args.length == 2 && block.isGiven()) {
            getRuntime().getWarnings().warn(ID.BLOCK_BEATS_DEFAULT_VALUE, "block supersedes default value argument");
        }

        var value:IRubyObject;
        if ((value = internalGet(args[0])) == null) {
            if (block.isGiven()) return block.yield(context, args[0]);
            if (args.length == 1) throw getRuntime().newIndexError("key not found");
            return args[1];
        }
        return value;
    }

    /** rb_hash_has_key
     *
     */
    public  function has_key_p(key:IRubyObject):RubyBoolean{
        return internalGetEntry(key) == NO_ENTRY ? getRuntime().getFalse() : getRuntime().getTrue();
    }

    private  function hasValue(context:ThreadContext, expected:IRubyObject):Boolean{
        try {
            visitAll(
                function visit(key:IRubyObject, value:IRubyObject):void{
                    if (equalInternal(context, value, expected).isTrue()) {
                        throw new Found();
                    }
                }
            );
            return false;
        } catch (found:Found) {
            return true;
        }
    }

    /** rb_hash_has_value
     *
     */
    public  function has_value_p(context:ThreadContext, expected:IRubyObject):RubyBoolean{
        return getRuntime().newBoolean(hasValue(context, expected));
    }

    /** rb_hash_each
     *
     */
    public  function each(context:ThreadContext, block:Block):RubyHash{
        var runtime: Ruby =  getRuntime();

        visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                // rb_assoc_new equivalent
                block.yield(context, RubyArray.newArray(runtime, key, value), null, null, false);
            }
        );

        return this;
    }

    /** rb_hash_each_pair
     *
     */
    public  function each_pair(context:ThreadContext, block:Block):RubyHash{
        var runtime: Ruby =  getRuntime();

        visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                // rb_yield_values(2,...) equivalent
                block.yield(context, RubyArray.newArray(runtime, key, value), null, null, true);
            }
        );

        return this;	
    }

    /** rb_hash_each_value
     *
     */
    public  function each_value(context:ThreadContext, block:Block):RubyHash{
        visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                block.yield(context, value);
            }
        );

        return this;
    }

    /** rb_hash_each_key
     *
     */
    public  function each_key(context:ThreadContext, block:Block):RubyHash{
        visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                block.yield(context, key);
            }
        );

        return this;
    }

    /** rb_hash_sort
     *
     */
    public  function sort(block:Block):RubyArray{
        return to_a().sort_bang(block);
    }

    /** rb_hash_index
     *
     */
    public  function index(context:ThreadContext, expected:IRubyObject):IRubyObject{
        var key: IRubyObject =  internalIndex(context, expected);
        if (key != null) {
            return key;
        } else {
            return getRuntime().getNil();
        }
    }

    private  function internalIndex(context:ThreadContext, expected:IRubyObject):IRubyObject{
        try {
            visitAll(
                function visit(key:IRubyObject, value:IRubyObject):void{
                    if (equalInternal(context, value, expected).isTrue()) {
                        throw new FoundKey(key);
                    }
                }
            );
            return null;
        } catch (found:FoundKey) {
            return found.key;
        }
    }

    /** rb_hash_indexes
     *
     */
    public  function indices(context:ThreadContext, indices:Array):RubyArray{
        return values_at(context, indices);
    }

    /** rb_hash_keys
     *
     */
    public  function keys():RubyArray{
        var runtime: Ruby =  getRuntime();
        var keys: RubyArray =  RubyArray.newArray(runtime, size);

        visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                keys.append(key);
            }
        );

        return keys;
    }

    /** rb_hash_values
     *
     *
          doc = 
          "Returns a new array populated with the values from hsh</i>. See\n"+
          "also Hash#keys</code>.\n"+
          "   \n"+
          "   h = { \"a\" => 100, \"b\" => 200, \"c\" => 300 }\n"+
          "   h.values   #=> [100, 200, 300]\n"
    )*/
    public  function rb_values():RubyArray{
        var values: RubyArray =  RubyArray.newArray(getRuntime(), size);

        visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                values.append(value);
            }
        );

        return values;
    }

    /** rb_hash_equal
     *
     */

    private static var EQUAL_CHECK_DEFAULT_VALUE: Boolean =  false;

    public override function op_equal(context:ThreadContext, other:IRubyObject):IRubyObject{
        if (this == other) return getRuntime().getTrue();
        if (!(other instanceof RubyHash)) {
            if (!other.respondsTo("to_hash")) return getRuntime().getFalse();
            return equalInternal(context, other, this);
        }

        var otherHash: RubyHash =  other;
        if (size != otherHash.size) return getRuntime().getFalse();

        var runtime: Ruby =  getRuntime();

        if (EQUAL_CHECK_DEFAULT_VALUE) {
            if (!equalInternal(context, ifNone, otherHash.ifNone).isTrue() &&
               (flags & PROCDEFAULT_HASH_F) != (otherHash.flags & PROCDEFAULT_HASH_F)) return runtime.getFalse();
        }

        try {
             visitAll(
                 function visit(key:IRubyObject, value:IRubyObject):void{
                     var otherValue: IRubyObject =  otherHash.internalGet(key);
                     if (otherValue == null || !equalInternal(context, value, otherValue).isTrue()) throw new Mismatch();
                 }
             );
             return runtime.getTrue();
        } catch (e:Mismatch) {
             return runtime.getFalse();
        }
    }

    /** rb_hash_shift
     *
     */
    public  function shift(context:ThreadContext):IRubyObject{
        modify();

        var entry: RubyHashEntry =  head.nextAdded;
        if (entry != head) {
            var result: RubyArray =  RubyArray.newArray(getRuntime(), entry.key, entry.value);
            internalDeleteEntry(entry);
            return result;
        }

        if ((flags & PROCDEFAULT_HASH_F) != 0) {
            return ifNone.callMethod(context, "call", [this, getRuntime().getNil()]);
        } else {
            return ifNone;
        }
    }

    public  function fastDelete(key:IRubyObject):Boolean{
        return internalDelete(key) != NO_ENTRY;
    }

    /** rb_hash_delete
     *
     */
    public  function delete0(context:ThreadContext, key:IRubyObject, block:Block):IRubyObject{
        modify();

        var entry: RubyHashEntry =  internalDelete(key);
        if (entry != NO_ENTRY) return entry.value;

        if (block.isGiven()) return block.yield(context, key);
        return getRuntime().getNil();
    }

    /** rb_hash_select
     *
     */
    public  function select(context:ThreadContext, block:Block):IRubyObject{
        var result: RubyArray =  getRuntime().newArray();
        var runtime: Ruby =  getRuntime();

        visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                if (block.yield(context, runtime.newArray(key, value), null, null, true).isTrue()) {
                    result.append(runtime.newArray(key, value));
                }
            }
        );

        return result;
    }

    /** rb_hash_delete_if
     *
     */
    public  function delete_if(context:ThreadContext, block:Block):RubyHash{
        modify();

        var runtime: Ruby =  getRuntime();
        var self: RubyHash =  this;
        visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                if (block.yield(context, RubyArray.newArray(runtime, key, value), null, null, true).isTrue()) {
                    self.delete0(context, key, block);
                }
            }
        );

        return this;
    }

    /** rb_hash_reject
     *
     */
    public  function reject(context:ThreadContext, block:Block):RubyHash{
        return (dup()).delete_if(context, block);
    }

    /** rb_hash_reject_bang
     *
     */
    public  function reject_bang(context:ThreadContext, block:Block):IRubyObject{
        var n: int =  size;
        delete_if(context, block);
        if (n == size) return getRuntime().getNil();
        return this;
    }

    /** rb_hash_clear
     *
     */
    public  function rb_clear():RubyHash{
        modify();

        if (size > 0) {
            alloc();
            size = 0;
        }

        return this;
    }

    /** rb_hash_invert
     *
     */
    public  function invert(context:ThreadContext):RubyHash{
        var result: RubyHash =  newHash(getRuntime());

        visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                result.op_aset(context, value, key);
            }
        );

        return result;
    }

    /** rb_hash_update
     *
     */
    public  function merge_bang(context:ThreadContext, other:IRubyObject, block:Block):RubyHash{
        modify();

        var runtime: Ruby =  getRuntime();
        var otherHash: RubyHash =  other.convertToHash();
        var self: RubyHash =  this;
        otherHash.visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                if (block.isGiven()) {
                    var existing: IRubyObject =  self.internalGet(key);
                    if (existing != null)
                        value = block.yield(context, RubyArray.newArrayNoCopy(runtime, [key, existing, value]));
                }
                self.op_aset(context, key, value);
            }
        );

        return this;
    }

    /** rb_hash_merge
     *
     */
    public  function merge(context:ThreadContext, other:IRubyObject, block:Block):RubyHash{
        return (dup()).merge_bang(context, other, block);
    }

    /** rb_hash_replace
     *
     */
    public override function initialize_copy(context:ThreadContext, other:IRubyObject):RubyHash{
        return replace(context, other);
    }

    /** rb_hash_replace
     *
     */
    public  function replace(context:ThreadContext, other:IRubyObject):RubyHash{
        var otherHash: RubyHash =  other.convertToHash();

        if (this == otherHash) return this;

        rb_clear();

        var self: RubyHash =  this;
        otherHash.visitAll(
            function visit(key:IRubyObject, value:IRubyObject):void{
                self.op_aset(context, key, value);
            }
        );

        ifNone = otherHash.ifNone;

        if ((otherHash.flags & PROCDEFAULT_HASH_F) != 0) {
            flags |= PROCDEFAULT_HASH_F;
        } else {
            flags &= ~PROCDEFAULT_HASH_F;
        }

        return this;
    }

    /** rb_hash_values_at
     *
     */
    public  function values_at(context:ThreadContext, args:Array):RubyArray{
        var result: RubyArray =  RubyArray.newArray(getRuntime(), args.length);
        for ( var int:i = 0; i < args.length; i++) {
            result.append(op_aref(context, args[i]));
        }
        return result;
    }

    public  function hasDefaultProc():Boolean{
        return (flags & PROCDEFAULT_HASH_F) != 0;
    }

    public  function getIfNone():IRubyObject{
        return ifNone;
    }

    // FIXME:  Total hack to get flash in Rails marshalling/unmarshalling in session ok...We need
    // to totally change marshalling to work with overridden core classes.
/*     public static function marshalTo(hash:RubyHash, output:MarshalStream):void{
        output.registerLinkTarget(hash);
        output.writeInt(hash.size);
        try {
            hash.visitAll(
                function visit(key:IRubyObject, value:IRubyObject):void{
                    try {
                        output.dumpObject(key);
                        output.dumpObject(value);
                    } catch (e:IOException) {
                        throw new VisitorIOException(e);
                    }
                }
            );
        } catch (e:VisitorIOException) {
            throw e.getCause();
        }

        if (!hash.ifNone.isNil()) output.dumpObject(hash.ifNone);
    }

    public static function unmarshalFrom(input:UnmarshalStream, defaultValue:Boolean):RubyHash{
        var result: RubyHash =  newHash(input.getRuntime());
        input.registerLinkTarget(result);
        var size: int =  input.unmarshalInt();
        var context: ThreadContext =  input.getRuntime().getCurrentContext();
        for ( var int:i = 0; i < size; i++) {
            result.op_aset(context, input.unmarshalObject(), input.unmarshalObject());
        }
        if (defaultValue) result.default_value_set(input.unmarshalObject());
        return result;
    } */

    public override function getJavaClass():Class{
        return Map;
    }

    // Satisfy java.util.Set interface (for Java integration)

    public function size():int{
        return size;
    }

    public  function isEmpty():Boolean{
        return size == 0;
    }

    public  function containsKey(key:Object):Boolean{
        return internalGet(JavaUtil.convertJavaToRuby(getRuntime(), key)) != null;
    }

    public  function containsValue(value:Object):Boolean{
        return hasValue(getRuntime().getCurrentContext(), JavaUtil.convertJavaToRuby(getRuntime(), value));
    }

    public  function get(key:Object):Object{
        return JavaUtil.convertRubyToJava(internalGet(JavaUtil.convertJavaToRuby(getRuntime(), key)));
    }

    public  function put(key:Object, value:Object):Object{
        internalPut(JavaUtil.convertJavaToRuby(getRuntime(), key), JavaUtil.convertJavaToRuby(getRuntime(), value));
        return value;
    }

    public  function remove(key:Object):Object{
        var rubyKey: IRubyObject =  JavaUtil.convertJavaToRuby(getRuntime(), key);
        return internalDelete(rubyKey).value;
    }

    public  function putAll(map:Dictionary):void{
        var runtime: Ruby =  getRuntime();
        for ( var Iterator:iter = map.keySet().iterator(); iter.hasNext();) {
            var key: Object =  iter.next();
            internalPut(JavaUtil.convertJavaToRuby(runtime, key), JavaUtil.convertJavaToRuby(runtime, map.get(key)));
        }
    }

    public  function clear():void{
        rb_clear();
    }

    public override function equals(other:Object):Boolean{
        if (!(other instanceof RubyHash)) return false;
        if (this == other) return true;
        return op_equal(getRuntime().getCurrentContext(), other).isTrue() ? true : false;
    }

    public  function keySet():Array{
        return new BaseSet(KEY_VIEW);
    }

    public  function directKeySet():Array{
        return new BaseSet(DIRECT_KEY_VIEW);
    }

    public  function values():Array{
        return new BaseCollection(VALUE_VIEW);
    }

    public  function directValues():Array{
        return new BaseCollection(DIRECT_VALUE_VIEW);
    }

    public  function entrySet():Array{
        return new BaseSet(ENTRY_VIEW);
    }

    public  function directEntrySet():Array{
        return new BaseSet(DIRECT_ENTRY_VIEW);
    }

    /* private class BaseSet extends AbstractSet {
        var view:EntryView;

        public  function BaseSet(view:EntryView){
            this.view = view;
        }

        public  function iterator():Iterator{
            return new BaseIterator(view);
        }

        public  function contains(o:Object):Boolean{
            return view.contains(RubyHash.this, o);
        }

        public  function clear():void{
            RubyHash.this.clear();
        }

        public  function size():int{
            return RubyHash.this.size;
        }

        public  function remove(o:Object):Boolean{
            return view.remove(RubyHash.this, o);
        }
    }

    private class BaseCollection extends AbstractCollection {
        var view:EntryView;

        public  function BaseCollection(view:EntryView){
            this.view = view;
        }

        public  function iterator():Iterator{
            return new BaseIterator(view);
        }

        public  function contains(o:Object):Boolean{
            return view.contains(RubyHash.this, o);
        }

        public  function clear():void{
            RubyHash.this.clear();
        }

        public  function size():int{
            return RubyHash.this.size;
        }

        public  function remove(o:Object):Boolean{
            return view.remove(RubyHash.this, o);
        }
    }

    private class BaseIterator implements Iterator {
        private  var view:EntryView;
        private  var entry:RubyHashEntry;
        private  var peeking:Boolean;
        private  var startGeneration:int;

        public  function BaseIterator(view:EntryView){
            this.view = view;
            this.entry = head;
            this.startGeneration = generation;
        }

        private  function advance(consume:Boolean):void{
            if (!peeking) {
                do {
                    if (startGeneration != generation) {
                        startGeneration = generation;
                        entry = head;
                    }
                    entry = entry.nextAdded;
                } while (entry != head && !entry.isLive());
            }
            peeking = !consume;
        }

        public  function next():Object{
            advance(true);
            if (entry == head) {
                peeking = true; // remain where we are
                throw new NoSuchElementException();
            }
            return view.convertEntry(getRuntime(), entry);
        }

        // once hasNext has been called, we commit to next() returning
        // the entry it found, even if it were subsequently deleted
        public  function hasNext():Boolean{
            advance(false);
            return entry != head;
        }

        public  function remove():void{
            if (entry == head) {
                throw new IllegalStateException("Iterator out of range");
            }
            internalDeleteEntry(entry);
        }
    }

    private static abstract class EntryView {
        public abstract Object convertEntry(Ruby runtime, RubyHashEntry value);
        public abstract boolean contains(RubyHash hash, Object o);
        public abstract boolean remove(RubyHash hash, Object o);
    }

    private static var DIRECT_KEY_VIEW: EntryView =  new EntryView() {
        public  function convertEntry(runtime:Ruby, entry:RubyHashEntry):Object{
            return entry.key;
        }
        public  function contains(hash:RubyHash, o:Object):Boolean{
            if (!(o instanceof IRubyObject)) return false;
            return hash.internalGet((IRubyObject)o) != null;
        }
        public  function remove(hash:RubyHash, o:Object):Boolean{
            if (!(o instanceof IRubyObject)) return false;
            return hash.internalDelete((IRubyObject)o) != NO_ENTRY;
        }
    };

    private static var KEY_VIEW: EntryView =  new EntryView() {
        public  function convertEntry(runtime:Ruby, entry:RubyHashEntry):Object{
            return JavaUtil.convertRubyToJava(entry.key, Object);
        }
        public  function contains(hash:RubyHash, o:Object):Boolean{
            return hash.containsKey(o);
        }
        public  function remove(hash:RubyHash, o:Object):Boolean{
            return hash.remove(o) != null;
        }
    };

    private static var DIRECT_VALUE_VIEW: EntryView =  new EntryView() {
        public  function convertEntry(runtime:Ruby, entry:RubyHashEntry):Object{
            return entry.value;
        }
        public  function contains(hash:RubyHash, o:Object):Boolean{
            if (!(o instanceof IRubyObject)) return false;
            var obj: IRubyObject =  (IRubyObject)o;
            return hash.hasValue(obj.getRuntime().getCurrentContext(), obj);
        }
        public  function remove(hash:RubyHash, o:Object):Boolean{
            if (!(o instanceof IRubyObject)) return false;
            var obj: IRubyObject =  (IRubyObject) o;
            var key: IRubyObject =  hash.internalIndex(obj.getRuntime().getCurrentContext(), obj);
            if (key == null) return false;
            return hash.internalDelete(key) != NO_ENTRY;
        }
    };

    private  var VALUE_VIEW: EntryView =  new EntryView() {
        public  function convertEntry(runtime:Ruby, entry:RubyHashEntry):Object{
            return JavaUtil.convertRubyToJava(entry.value, Object);
        }
        public  function contains(hash:RubyHash, o:Object):Boolean{
            return hash.containsValue(o);
        }
        public  function remove(hash:RubyHash, o:Object):Boolean{
            var value: IRubyObject =  JavaUtil.convertJavaToRuby(hash.getRuntime(), o);
            var key: IRubyObject =  hash.internalIndex(hash.getRuntime().getCurrentContext(), value);
            if (key == null) return false;
            return hash.internalDelete(key) != NO_ENTRY;
        }
    };

    private  var DIRECT_ENTRY_VIEW: EntryView =  new EntryView() {
        public  function convertEntry(runtime:Ruby, entry:RubyHashEntry):Object{
            return entry;
        }
        public  function contains(hash:RubyHash, o:Object):Boolean{
            if (!(o instanceof RubyHashEntry)) return false;
            var entry: RubyHashEntry =  (RubyHashEntry)o;
            var candidate: RubyHashEntry =  internalGetEntry(entry.key);
            return candidate != NO_ENTRY && entry.equals(candidate);
        }
        public  function remove(hash:RubyHash, o:Object):Boolean{
            if (!(o instanceof RubyHashEntry)) return false;
            return hash.internalDeleteEntry((RubyHashEntry)o) != NO_ENTRY;
        }
    };

    private  var ENTRY_VIEW: EntryView =  new EntryView() {
        public  function convertEntry(runtime:Ruby, entry:RubyHashEntry):Object{
            return new ConvertingEntry(runtime, entry);
        }
        public  function contains(hash:RubyHash, o:Object):Boolean{
            if (!(o instanceof ConvertingEntry)) return false;
            var entry: ConvertingEntry =  (ConvertingEntry)o;
            var candidate: RubyHashEntry =  hash.internalGetEntry(entry.entry.key);
            return candidate != NO_ENTRY && entry.entry.equals(candidate);
        }
        public  function remove(hash:RubyHash, o:Object):Boolean{
            if (!(o instanceof ConvertingEntry)) return false;
            var entry: ConvertingEntry =  (ConvertingEntry)o;
            return hash.internalDeleteEntry(entry.entry) != NO_ENTRY;
        }
    };

    private static class ConvertingEntry implements Map.Entry {
        private  var entry:RubyHashEntry;
        private  var runtime:Ruby;

        public  function ConvertingEntry(runtime:Ruby, entry:RubyHashEntry){
            this.entry = entry;
            this.runtime = runtime;
        }

        public  function getKey():Object{
            return JavaUtil.convertRubyToJava(entry.key, Object);
        }
        public  function getValue():Object{
            return JavaUtil.convertRubyToJava(entry.value, Object);
        }
        public  function setValue(o:Object):Object{
            return entry.setValue(JavaUtil.convertJavaToRuby(runtime, o));
        }

        public  function equals(o:Object):Boolean{
            if (!(o instanceof ConvertingEntry)) {
                return false;
            }
            var other: ConvertingEntry =  (ConvertingEntry)o;
            return entry.equals(other.entry);
        }
        public  function hashCode():int{
            return entry.hashCode();
        }
    } */
}
}